﻿#region "License Agreement"
/* ====================================================================
* 
* The Apache Software License, Version 1.1
*
* Copyright (c) 2003 Validity Systems Inc.  All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution, if
*    any, must include the following acknowlegement:
*       "This product includes software developed by the
*        Validity Systems Inc."
*    Alternately, this acknowlegement may appear in the software itself,
*    if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "#Logger", "CS Logger", and "Validity Systems
*    Inc." must not be used to endorse or promote products derived
*    from this software without prior written permission. For written
*    permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "#Logger"
*    nor may "#Logger" appear in their names without prior written
*    permission of Validity Systems Inc.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Validity Systems Inc.
*
*
*/

/*
 *  Author: Athony La Forge (Validity Systems Inc.) 
 *  Author2: Affan S from Toronto Canada contributing minor changes to code
 */
#endregion
using System;
using System.ComponentModel; 
using System.IO; 
using System.Diagnostics;
using System.Collections;
using System.Threading;



/// <summary>
/// Represents a log message
/// </summary>
public struct LoggerMessage
{
    public int level;
    public string level_desc;
    public string tag;
    public string message;
    public long time;
}

/// <summary>
/// Primary class responsbile for logging.  This class is a combination between a singleton
/// and a regular class to allow for the developers to have more customability.
/// 
/// One thing worth mentioning, the only thing that this system will not allow developers to
/// customize is the use of consecutive integers for log levels starting at 0.  Developers are
/// free to use however many levels they want, define them however they want, however... that is the
/// one limitation I am comfortable placing on them.
/// </summary>
public class Logger
{
    private static Logger logger;
    protected static string[] logLevelDesc = null;
    protected LoggerEventHandler[][] leh;
    protected uint max = 0;
    protected uint levels = 0;
    protected LoggerEventHandler defaultHandler = null;

    /// <summary>
    /// With this constructor the developer is responsible for defining what
    /// they want the logger to do (in the defaultHandler).
    /// </summary>
    /// <param name="levels"></param>
    /// <param name="defaultHandler"></param>
    public Logger(uint levels, LoggerEventHandler defaultHandler)
    {
        init(levels, defaultHandler);
    }

    /// <summary>
    /// Opens up a standard to file logger with the specified number of log levels.
    /// </summary>
    /// <param name="levels"></param>
    /// <param name="filename"></param>
    public Logger(uint levels, string filename)
    {
        init(levels, new BasicFileLogger(filename));
    }

    /// <summary>
    /// User specifies only the number of log levels they require.  They are still obligated
    /// (assuming they want the logger to do something) to add a handler.
    /// </summary>
    /// <param name="levels"></param>
    public Logger(uint levels)
    {
        init(levels, null);
    }

    /// <summary>
    /// Effectively the constructor (so no code would have to be repeated).  Consequently
    /// if one were to try to put this block inside the constructor matching the signature and
    /// then referenced it with :this(levels, defaultHandler) in the other constructors MS
    /// has a bit of a fit.  Dunno...this works, don't really care.
    /// </summary>
    /// <param name="levels"></param>
    /// <param name="defaultHandler"></param>
    private void init(uint levels, LoggerEventHandler defaultHandler)
    {
        this.levels = levels;
        this.defaultHandler = defaultHandler;
        this.max = levels - 1;

        leh = new LoggerEventHandler[levels][];

        LoggerEventHandler[] handler = new LoggerEventHandler[1];

        handler[0] = defaultHandler;

        for (int i = 0; i < levels; i++)
        {
            leh[i] = handler;
        }
    }


    /// <summary>
    /// Takes the current object and places it as the internal singleton reference
    /// </summary>
    /// <returns></returns>
    public Logger promoteToStatic()
    {
        logger = this;
        return logger;
    }


    /// <summary>
    /// The singleton can get set in two ways:
    /// 
    /// 1.) The developer promotes their class to be the Logger object inside the singleton
    /// 2.) We decide for them what their class is going to look like
    /// </summary>
    /// <returns></returns>
    public static Logger singleton()
    {
        if (logger == null)
        {
            //use defaults
            string logFile = DateTime.Now.ToShortDateString().Replace(@"/", @"-").Replace(@"\", @"-") + ".log";
            logger = new Logger(6, logFile);
            logLevelDesc = new string[6];
            logLevelDesc[0] = "V_CRITICAL";
            logLevelDesc[1] = "V_ERROR";
            logLevelDesc[2] = "V_WARN";
            logLevelDesc[3] = "V_INFO";
            logLevelDesc[4] = "V_DEBUG";
            logLevelDesc[5] = "V_ALL";
        }

        return logger;
    }


    /// <summary>
    /// This is the maximum level which will trigger logging.
    /// </summary>
    /// <param name="min"></param>
    public void setMaximumLogLevel(uint max)
    {
        this.max = max;
    }

    /// <summary>
    /// Retrieve maximum logging level (note: this is not the total number of levels, but rather
    /// the upper bound where an action will or won't take placed based on the log level)
    /// </summary>
    /// <returns></returns>
    public uint getMaximumLogLevel()
    {
        return max;
    }

    /// <summary>
    /// Retreives the default handler if one is set
    /// </summary>
    /// <returns></returns>
    public LoggerEventHandler getDefaultLoggerEventHandler()
    {
        return this.defaultHandler;
    }

    /// <summary>
    /// Adds a customized log handler to each log level
    /// </summary>
    /// <param name="handler"></param>
    public void addSpecialLoggerToAllLevels(LoggerEventHandler handler)
    {
        if (handler == null) return;

        for (int level = 0; level < this.levels; level++)
        {
            addSpecialLogger(level, handler);
        }
    }

    /// <summary>
    /// Adds a customized logger handler (e.g. log to file) to a specific level.
    /// Note: You can have (n log handlers assuming your system has the resources)
    /// </summary>
    /// <param name="level"></param>
    /// <param name="handler"></param>
    public void addSpecialLogger(int level, LoggerEventHandler handler)
    {
        if (level < levels)
        {
            if (leh[level] != null)
            {
                int size = leh[level].Length + 1;
                LoggerEventHandler[] temp = new LoggerEventHandler[size];

                for (int i = 0; i < leh[level].Length; i++)
                {
                    temp[i] = leh[level][i];
                }

                temp[size - 1] = handler;

                leh[level] = temp;
            }
            else
            {
                leh[level] = new LoggerEventHandler[1];
                leh[level][0] = handler;
            }
        }
    }

    /// <summary>
    /// Sugared method to add a simple file handler method
    /// </summary>
    /// <param name="level"></param>
    /// <param name="filename"></param>
    public void addSpecialLogger(int level, string filename)
    {
        addSpecialLogger(level, new BasicFileLogger(filename));
    }

    /// <summary>
    /// Invokes all appropriate log event handlers with the message
    /// </summary>
    /// <param name="level"></param>
    /// <param name="tag"></param>
    /// <param name="message"></param>
    public void log(int level, string tag, string message)
    {
        if ((level <= max) && (level < levels) && (leh[level] != null))
        {
            for (int i = 0; i < leh[level].Length; i++)
            {
                if (logLevelDesc == null)
                {
                    if (leh[level][i] != null) leh[level][i].log(tag, level, "", message);
                }
                else
                {
                    if (leh[level][i] != null) leh[level][i].log(tag, level, logLevelDesc[level], message);
                }
            }
        }
    }

    /// <summary>
    /// Invokes the shutdown method for all log handlers
    /// </summary>
    public void shutdown()
    {
        //This is not the most efficient, really I'd like to add a collection
        //in this class and loop through it (save some repeats), also allow for some other neet stuff.
        for (int level = 0; level < leh.Length; level++)
        {
            for (int i = 0; i < leh[level].Length; i++)
            {
                if (leh[level][i] != null) leh[level][i].shutdown();
            }
        }
    }

}



/// <summary>
/// Basic Log that writes to a text file. Inherits from LoggerEventHandler
/// </summary>
public class BasicFileLogger : LoggerEventHandler
{
    StreamWriter stream = null;
    bool append = true;
    public bool AppendMode { get {return append;} set{append = value;} }

    public BasicFileLogger(String filename)
    {
        if (!string.IsNullOrEmpty(filename))
        {

            FileMode fm;
            if (append)
            {
                fm = FileMode.Append;
            }
            else
            {
                fm = FileMode.Create;
            }

            FileStream fs = new FileStream(filename, fm, FileAccess.Write, FileShare.Read);
            stream = new StreamWriter(fs, System.Text.Encoding.UTF8, 4096);
        }

    }

    override protected void log(LoggerMessage message)
    {
        if (stream == null) return;
        string time = System.DateTime.FromFileTime(message.time).ToString();
        string test = "[" + time + " [" + message.level + ":" + message.level_desc + " (" + message.tag + ")] " + message.message + " ]\r\n";
        stream.Write("[" + time + " [" + message.level + ":" + message.level_desc + " (" + message.tag + ")] " + message.message + " ]\r\n");
    }

    protected override void onShutdown()
    {
        if (stream != null)
        {
            stream.Flush();
            stream.Close();
        }
    }
    

}


/// <summary>
/// Abstract class responsible managing (in the form or a queue) and dispatching log messages
/// to their appropriate source.  The basic premises is that everything from a file writers, 
/// to an e-mailer, to an syslog interface, to a db interface, etc... could be implemented and the only
/// thing the implementer would need to worry about was the interface.
/// </summary>
public abstract class LoggerEventHandler
{

    //on the thread (var: dispatch) a queue (var: q) is run eventually clearing the queue.
    //member variables
    private bool alive = false;
    private Queue q = null;
    private Thread dispatch = null;
    protected string[] logLevelDescriptors = null;

    public LoggerEventHandler()
    {
        q = Queue.Synchronized(new Queue(1000));
        start();
    }


    /// <summary>
    /// Starts the execution of the Handler (e.g. Queue goes live)
    /// </summary>
    public void start()
    {
        //It's already alive, nothing to do
        if (alive) 
            return;

        //Make sure this is the only place where alive is set to true
        alive = true;
        dispatch = new Thread(new ThreadStart(dispatchMessages));
        dispatch.Start();
    }

    /// <summary>
    /// Stops the execution of the Handler gracefully (e.g. everything in the queue is
    /// dispatched, but nothing can be added to it). Note if the thread is still alive
    /// this method does nothing.
    /// </summary>
    public void shutdown()
    {
        //Nothing to do
        if (!alive) return;

        alive = false;
        Monitor.Enter(q);
        Monitor.PulseAll(q);
        Monitor.Exit(q);
    }

    /// <summary>
    /// Will immediately shutdown the thread without cleaning up or clearing
    /// out the queue.  Consequently this is not the recommended way to terminate.
    /// </summary>
    public void abort()
    {
        if (!alive) return;

        alive = false;
        dispatch.Abort();
    }


    /// <summary>
    /// This is the worker method where the messages get dispatched
    /// </summary>
    protected void dispatchMessages()
    {
        //int max = 0;
        while (alive)
        {
            while ((q.Count != 0) && alive)
            {
                //cast the popped object of the queue to structure
                log((LoggerMessage)q.Dequeue()); 
            }

            if ((alive) && (q.Count == 0))
            {
                Monitor.Enter(q);
                if (q.Count == 0) Monitor.Wait(q);
                Monitor.Exit(q);
            }
        }

        //You will only reach this code if you are exiting the program
        //This block ensures the messages that were queued up will get dumped
        while (q.Count != 0)
        {
            log((LoggerMessage)q.Dequeue());
        }

        //Do any special shutdown you need to do
        onShutdown();

        //Remove thread reference so you don't have object leak
        dispatch = null;
    }


    /// <summary>
    /// Responsible for queuing the log message
    /// </summary>
    /// <param name="tag"></param>
    /// <param name="level"></param>
    /// <param name="level_desc"></param>
    /// <param name="message"></param>
    public void log(string tag, int level, string level_desc, string message)
    {
        if (!alive) return;

        LoggerMessage lm = new LoggerMessage();
        lm.message = message;
        lm.tag = tag;
        lm.level = level;
        lm.level_desc = level_desc;
        lm.time = System.DateTime.Now.ToFileTime();

        q.Enqueue(lm);

        Monitor.Enter(q);
        Monitor.PulseAll(q); //this notifies threads that object has changed
        Monitor.Exit(q);
    }

    /// <summary>
    /// User implementable log method (e.g. what actions they need to take to perform
    /// a "log" operation, like logging to a file for example).
    /// </summary>
    /// <param name="message"></param>
    protected abstract void log(LoggerMessage message);



    /// <summary>
    /// User implementable, meant for cleanup when the thread is stopped (e.g. if you need to
    /// close files, db connections, etc...)
    /// </summary>
    protected abstract void onShutdown();

}